//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#ifndef _UXS_GL_UNIFORM_H
#define _UXS_GL_UNIFORM_H

#pragma once

#include "GLShader.h"
#include "GLMesh.h"

namespace UX_STL
{
namespace GLShell
{

	//
	// Cached Uniform Setter/Getter
	//
	
	template <typename T>
	struct GLCachedUniformIO
	{
		enum {
			typeId	= 0
		};
		typedef T	type;

		static void Set (GLuint location, const T& oldValue, const T& newValue) {}
		static void Get (const GLProgram &prog, GLuint location, T& value) {}
	};


#	define DECL_UNIFORM_TYPE( _type, _typeId, _set, _get, _cmp ) \
		template <> \
		struct GLCachedUniformIO <_type> \
		{ \
			enum { \
				typeId = _typeId \
			}; \
			typedef _type		type; \
			\
			static void Set (GLuint location, const _type& oldValue, const _type& newValue) \
			{ \
				if ( not (_cmp) ) \
					GL_CALL( _set ); \
			} \
			\
			static void Get (const GLProgram &prog, GLuint location, _type& value) \
			{ \
				GLuint program = prog.Id(); \
				GL_CALL( _get ); \
			} \
		}

	DECL_UNIFORM_TYPE(	int,
						GL_INT,
						glUniform1i( location, newValue ),
						glGetUniformiv( program, location, &value ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	ivec2,
						GL_INT_VEC2,
						glUniform2iv( location, 1, newValue.ptr() ),
						glGetUniformiv( program, location, value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	ivec3,
						GL_INT_VEC3,
						glUniform3iv( location, 1, newValue.ptr() ),
						glGetUniformiv( program, location, value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	ivec4,
						GL_INT_VEC4,
						glUniform4iv( location, 1, newValue.ptr() ),
						glGetUniformiv( program, location, value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	uint,
						GL_UNSIGNED_INT,
						glUniform1ui( location, newValue ),
						glGetUniformiv( program, location, (GLint *)&value ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	uvec2,
						GL_UNSIGNED_INT_VEC2,
						glUniform2uiv( location, 1, newValue.ptr() ),
						glGetUniformiv( program, location, (GLint *)value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	uvec3,
						GL_UNSIGNED_INT_VEC3,
						glUniform3uiv( location, 1, newValue.ptr() ),
						glGetUniformiv( program, location, (GLint *)value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	uvec4,
						GL_UNSIGNED_INT_VEC4,
						glUniform4uiv( location, 1, newValue.ptr() ),
						glGetUniformiv( program, location, (GLint *)value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	float,
						GL_FLOAT,
						glUniform1f( location, newValue ),
						glGetUniformfv( program, location, &value ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	fvec2,
						GL_FLOAT_VEC2,
						glUniform2fv( location, 1, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	fvec3,
						GL_FLOAT_VEC3,
						glUniform3fv( location, 1, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	fvec4,
						GL_FLOAT_VEC4,
						glUniform4fv( location, 1, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	double,
						GL_DOUBLE,
						glUniform1d( location, newValue ),
						glGetUniformdv( program, location, &value ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	dvec2,
						GL_DOUBLE_VEC2,
						glUniform2dv( location, 1, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	dvec3,
						GL_DOUBLE_VEC3,
						glUniform3dv( location, 1, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	dvec4,
						GL_DOUBLE_VEC4,
						glUniform4dv( location, 1, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						All( oldValue == newValue )
					 );

	DECL_UNIFORM_TYPE(	fmat2,
						GL_FLOAT_MAT2,
						glUniformMatrix2fv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	fmat2x3,
						GL_FLOAT_MAT2x3,
						glUniformMatrix2x3fv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	fmat2x4,
						GL_FLOAT_MAT2x4,
						glUniformMatrix2x4fv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	fmat3x2,
						GL_FLOAT_MAT3x2,
						glUniformMatrix3x2fv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	fmat3,
						GL_FLOAT_MAT3,
						glUniformMatrix3fv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	fmat3x4,
						GL_FLOAT_MAT3x4,
						glUniformMatrix3x4fv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	fmat4x2,
						GL_FLOAT_MAT4x2,
						glUniformMatrix4x2fv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	fmat4x3,
						GL_FLOAT_MAT4x3,
						glUniformMatrix4x3fv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	fmat4,
						GL_FLOAT_MAT4,
						glUniformMatrix4fv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformfv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	dmat2,
						GL_DOUBLE_MAT2,
						glUniformMatrix2dv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	dmat2x3,
						GL_DOUBLE_MAT2x3,
						glUniformMatrix2x3dv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	dmat2x4,
						GL_DOUBLE_MAT2x4,
						glUniformMatrix2x4dv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	dmat3x2,
						GL_DOUBLE_MAT3x2,
						glUniformMatrix3x2dv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	dmat3,
						GL_DOUBLE_MAT3,
						glUniformMatrix3dv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	dmat3x4,
						GL_DOUBLE_MAT3x4,
						glUniformMatrix3x4dv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	dmat4x2,
						GL_DOUBLE_MAT4x2,
						glUniformMatrix4x2dv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	dmat4x3,
						GL_DOUBLE_MAT4x3,
						glUniformMatrix4x3dv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						oldValue == newValue
					 );

	DECL_UNIFORM_TYPE(	dmat4,
						GL_DOUBLE_MAT4,
						glUniformMatrix4dv( location, 1, GL_FALSE, newValue.ptr() ),
						glGetUniformdv( program, location, value.ptr() ),
						oldValue == newValue
					 );


	
	//
	// Uniform
	//

	template <typename T>
	struct GLUniform
	{
	// variables
	private:
		GLuint	_id;
		T		_value;

		DEBUG_ONLY(
			GLuint				_progId;
			string				_name;
			gl_uniform::type	_type;
			uint				_size;	// array size
		);
		
	// methods
	public:
		GLUniform ();
		GLUniform (const GLProgram &prog, const char *name);

		bool GetLocation (const GLProgram &prog, const char *name);
		void SetValue (const T& value);

		bool IsValid () const	{ return _id != -1; }
	};



	//
	// Uniform Buffer/Block
	//

	template <typename T>
	struct GLUniformBlock
	{
	// variables
	private:
		GLBuffer	_buf;
		T			_data;
		GLuint		_id;
		GLuint		_bindingIndex;
		bool		_changed;

		DEBUG_ONLY(
			GLuint	_progId;
			string	_name;
			uint	_size;	// size in bytes
		);

	// methods
	public:
		GLUniformBlock ();
		GLUniformBlock (const GLProgram &prog, const char *name, uint bindingIndex = 0);

		bool GetLocation (const GLProgram &prog, const char *name, uint bindingIndex = 0);
		void Destroy();
		bool Update ();

		T &			EditData()			{ _changed = true;  return _data; }
		T const &	ReadData() const	{ return _data; }

		bool		IsValid() const		{ return _id != -1; }
	};


	
//----------------------------- Uniform ---------------------------//
	
	template <typename T>
	inline GLUniform<T>::GLUniform() :
		_id(0), _value()
		DEBUG_ONLY( , _progId(0), _type(gl_uniform::_UNKNOWN), _size(0) )
	{}


	template <typename T>
	inline GLUniform<T>::GLUniform (const GLProgram &prog, const char *name) :
		_id(0), _value()
		DEBUG_ONLY( , _progId(0), _type(gl_uniform::_UNKNOWN), _size(0) )
	{
		CHECK( GetLocation( prog, name ) );
	}


	template <typename T>
	inline bool GLUniform<T>::GetLocation (const GLProgram &prog, const char *name)
	{
		_id = prog.GetUniform( name );
		CHECK_ERR( _id != -1 );

		DEBUG_ONLY(
			GLProgram::GLUniformInfo	info;
			CHECK( prog.GetUniformInfo( _id, info ) );

			_progId	= prog.Id();
			_name	= name;
			_size	= info.uCount;
			_type	= info.eType;
		)
		return true;
	}
	

	template <typename T>
	inline void GLUniform<T>::SetValue (const T& newValue)
	{
		GLCachedUniformIO<T>::Set( _id, _value, newValue );
		_value = newValue;
	}

	

//--------------------------- UniformBlock ------------------------//
	
	template <typename T>
	inline GLUniformBlock<T>::GLUniformBlock () :
		_id(-1), _bindingIndex(-1), _changed(false)
		DEBUG_ONLY( , _progId(0), _size(0) )
	{}


	template <typename T>
	inline GLUniformBlock<T>::GLUniformBlock (const GLProgram &prog, const char *name, uint bindingIndex) :
		_id(-1), _bindingIndex(-1), _changed(false)
		DEBUG_ONLY( , _progId(0), _size(0) )
	{
		CHECK( GetLocation( prog, name, bindingIndex ) );
	}


	template <typename T>
	inline bool GLUniformBlock<T>::GetLocation (const GLProgram &prog, const char *name, uint bindingIndex)
	{
		_id = prog.GetUniformBlockIndex( name );
		CHECK_ERR( _id != -1 );

		DEBUG_ONLY(
			_progId	= prog.Id();
			_name	= name;
			_size	= prog.GetUniformBlockSize( _id );
			ASSERT( _size == sizeof(_data) );
		);

		_bindingIndex = bindingIndex;
		CHECK_ERR( prog.BindBuffer( _id, _bindingIndex ) );

		CHECK_ERR( _buf.Create( gl_buffer::UNIFORM ) );
		CHECK_ERR( _buf.SetData( &_data, sizeof(_data), gl_usage::STREAM_DRAW ) );
		CHECK_ERR( _buf.BindBase( _bindingIndex ) );

		_changed = true;
		return true;
	}

	
	template <typename T>
	inline void GLUniformBlock<T>::Destroy()
	{
		_buf.Destroy();
		_id				= -1;
		_bindingIndex	= -1;
		_changed		= false;

		DEBUG_ONLY(
			_progId		= 0;
			_size		= 0;
		);
	}
	

	template <typename T>
	inline bool GLUniformBlock<T>::Update ()
	{
		if ( not _changed or not IsValid() )
			return true;

		CHECK_ERR( _buf.SetData( &_data, sizeof(_data), gl_usage::STREAM_DRAW ) );
		CHECK_ERR( _buf.BindBase( _bindingIndex ) );
		_changed = false;
		return true;
	}

	
//-------------------------------------------------------------------

}	// GLShell
}	// UX_STL

#endif	// _UXS_GL_UNIFORM_H